diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-07-01 15:30:44 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-07-03 22:43:44 -0400 |
| commit | 1cafeb5ec92c1dc4ad74fbed58b15a8ab2f3c0cf (patch) | |
| tree | 0ecca3ed4133210286e9d11b2d2027136f09113a /ui/routes/(app)/c/[conversation] | |
| parent | 8d412732dc094ead3c5cf86c005d187f9624fc65 (diff) | |
Move the `/ch` channel view to `/c` (for conversation).
Diffstat (limited to 'ui/routes/(app)/c/[conversation]')
| -rw-r--r-- | ui/routes/(app)/c/[conversation]/+page.svelte | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/ui/routes/(app)/c/[conversation]/+page.svelte b/ui/routes/(app)/c/[conversation]/+page.svelte new file mode 100644 index 0000000..4d2cc86 --- /dev/null +++ b/ui/routes/(app)/c/[conversation]/+page.svelte @@ -0,0 +1,117 @@ +<script> + import { DateTime } from 'luxon'; + import { page } from '$app/state'; + import MessageInput from '$lib/components/MessageInput.svelte'; + import MessageRun from '$lib/components/MessageRun.svelte'; + import Message from '$lib/components/Message.svelte'; + import { runs } from '$lib/runs.js'; + + const { data } = $props(); + const { session, outbox } = data; + let activeChannel; + + const channelId = $derived(page.params.conversation); + const channel = $derived(session.channels.find((channel) => channel.id === channelId)); + const messages = $derived( + session.messages.filter((message) => message.conversation === channelId), + ); + const unsent = $derived(outbox.messages.filter((message) => message.channel === channelId)); + const deleted = $derived(outbox.deleted.map((message) => message.messageId)); + const unsentSkeletons = $derived( + unsent.map((message) => message.toSkeleton($state.snapshot(session.currentUser))), + ); + const messageRuns = $derived(runs(messages.concat(unsentSkeletons), session.currentUser)); + + function inView(parentElement, element) { + const parRect = parentElement.getBoundingClientRect(); + const parentTop = parRect.top; + const parentBottom = parRect.bottom; + + const elRect = element.getBoundingClientRect(); + const elementTop = elRect.top; + const elementBottom = elRect.bottom; + + return parentTop < elementTop && parentBottom > elementBottom; + } + + function getLastVisibleMessage() { + if (activeChannel) { + const childElements = activeChannel.getElementsByClassName('message'); + const lastInView = Array.from(childElements) + .reverse() + .find((el) => { + return inView(activeChannel, el); + }); + return lastInView; + } + } + + function setLastRead() { + const lastInView = getLastVisibleMessage(); + const at = !!lastInView ? DateTime.fromISO(lastInView.dataset.at) : channel?.at; + if (!!at) { + session.local.updateLastReadAt(channelId, at); + } + } + + $effect(() => { + const _ = session.messages; + setLastRead(); + }); + + $effect(() => { + // This is just to force it to track messageRuns. + const _ = messageRuns; + document.querySelector('.message-run:last-child .message:last-child')?.scrollIntoView(); + }); + + function handleKeydown(event) { + if (event.key === 'Escape') { + setLastRead(); // TODO: pass in "last message DT"? + } + } + + let lastReadCallback = null; + + function onscroll() { + clearTimeout(lastReadCallback); // Fine if lastReadCallback is null still. + lastReadCallback = setTimeout(setLastRead, 2 * 1000); + } + + async function sendMessage(message) { + outbox.postToChannel(channelId, message); + } + + async function deleteMessage(id) { + outbox.deleteMessage(id); + } +</script> + +<svelte:window onkeydown={handleKeydown} /> + +<div class="active-channel" {onscroll} bind:this={activeChannel}> + {#each messageRuns as { sender, ownMessage, messages }} + <MessageRun + {sender} + class={{ + ['own-message']: ownMessage, + ['other-message']: !ownMessage, + }} + > + {#each messages as message} + <Message + {...message} + editable={ownMessage} + {deleteMessage} + class={{ + unsent: !message.id, + deleted: deleted.includes(message.id), + }} + /> + {/each} + </MessageRun> + {/each} +</div> +<div class="create-message"> + <MessageInput {sendMessage} /> +</div> |
